Skip to content

feat(contradictions): advisory scanner for conflicting approved claims#405

Open
jsdevninja wants to merge 3 commits into
vouchdev:testfrom
jsdevninja:feat/contradiction-scan
Open

feat(contradictions): advisory scanner for conflicting approved claims#405
jsdevninja wants to merge 3 commits into
vouchdev:testfrom
jsdevninja:feat/contradiction-scan

Conversation

@jsdevninja

@jsdevninja jsdevninja commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

closes #314

summary

  • adds src/vouch/contradictions.py: a read-only heuristic scanner that groups approved claims by shared Claim.entities and flags same-topic pairs whose text disagrees in polarity (one asserts, the other negates) as candidate contradictions.
  • adds vouch contradict-scan (cli-only, matching how vouch dedup ships without a dedicated kb.* method): --threshold, --entity, --limit, --dry-run/--no-dry-run (default dry-run).
  • dry-run only prints candidate pairs with a score and the shared entity. without --dry-run, each surviving pair files exactly one pending contradicts relation proposal via proposals.propose_relation, visible in kb.list_pending / vouch pending.
  • a pair already cross-linked via Claim.contradicts, already joined by an approved contradicts relation edge, or already carrying a pending contradicts proposal is skipped, so repeat scans don't pile up duplicates.
  • the scanner itself never sets ClaimStatus.CONTESTED, never writes a Relation, and never touches Claim.contradicts — all business logic lives in contradictions.py, storage.py is untouched.

design note (worth a second pair of eyes)

approving the resulting relation proposal currently lands a plain Relation(relation=CONTRADICTS) edge — it does not flip both claims to CONTESTED or cross-link Claim.contradicts. that full symmetric transition still only happens via the pre-existing, deliberately-manual lifecycle.contradict (kb.contradict), whose own docstring says it intentionally bypasses the proposal queue and warns against refactoring it for stricter gating. i kept proposals.approve() untouched rather than special-casing relation == "contradicts" there, since that's shared code for every relation kind and the issue's acceptance checklist only requires the proposal to land as a CONTRADICTS edge on approval, not the status flip. happy to wire that up instead if the intended reading of "the actual write still flows through lifecycle.contradict semantics on approval" was that kb.approve itself should trigger the full transition.

testing

  • tests/test_contradictions.py (15 tests): candidate scoring/grouping, entity/threshold/limit filters, inactive-claim exclusion, all three duplicate-skip paths (contradicts field / approved edge / pending proposal), dry-run writes nothing, non-dry-run files exactly one proposal per pair and never mutates claim status or writes a relation directly, repeat scans don't duplicate, and a full scan → propose → approve integration test landing a CONTRADICTS edge.
  • manually exercised vouch contradict-scan end-to-end against a scratch kb (dry-run, --no-dry-run, re-scan dedup, vouch approve) — matches the automated coverage.
  • ruff check src tests clean.
  • mypy src — same 3 pre-existing errors as main (sandbox.py os.getuid/os.getgid on non-posix, missing jinja2 stubs); none in the touched files.
  • pytest tests/ -q --ignore=tests/embeddings — ran locally on windows. two files (test_http_server.py, test_http_server_mcp.py) hang in this sandbox because they bind a real loopback socket, unrelated to this diff, so i excluded them locally; everything else passed except 8 pre-existing windows-only failures (posix os.getuid, unprivileged symlinks, / vs \ path assertions) untouched by this change. tests/test_contradictions.py is green.

test plan

  • vouch contradict-scan lists candidate pairs, writes nothing in --dry-run (default)
  • without --dry-run, each surviving pair produces exactly one pending contradicts proposal visible in kb.list_pending
  • scanner never sets CONTESTED, never writes a Relation, never touches Claim.contradicts
  • no duplicate proposals across repeat scans
  • --threshold, --entity, --limit, --dry-run/--no-dry-run behave as documented
  • scoring/grouping lives in src/vouch/contradictions.py, storage.py unchanged
  • cli-only for this first cut (no kb.* mcp/jsonl method), per the issue's stated allowance

Summary by CodeRabbit

  • New Features
    • Added vouch contradict-scan to scan approved claims for likely same-entity contradictions, ranking candidate pairs by similarity and negation polarity.
    • Supports --dry-run (default) to preview candidates, or live mode to create pending contradiction relation proposals for later approval.
    • Added --threshold, optional --entity scoping, and --limit to tune results.
  • Bug Fixes
    • Prevents duplicate or redundant proposals when contradictions already exist or have been proposed.
  • Tests
    • Added coverage for candidate detection, filtering rules, dry-run vs live writes, idempotency, limiting, and the approve flow.
  • Documentation
    • Updated the changelog with the new command and flag behavior.

@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface tests tests and fixtures size: M 200-499 changed non-doc lines labels Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5931ea33-a9b2-4650-84c9-908a2df35b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 561ac2f and 8590587.

📒 Files selected for processing (1)
  • CHANGELOG.md

Walkthrough

Adds an advisory vouch contradict-scan feature that groups approved claims by shared entity, detects likely polarity conflicts, optionally files pending relation proposals, exposes CLI controls, and adds tests from discovery through approval.

Changes

Contradiction Scan Feature

Layer / File(s) Summary
Scoring model and entity grouping
src/vouch/contradictions.py
Adds tokenization, negation detection, Jaccard scoring, the Candidate dataclass, and shared-entity grouping.
Candidate discovery and proposal scanning
src/vouch/contradictions.py
Filters inactive or previously flagged pairs, identifies opposite-polarity candidates, and optionally creates pending contradicts proposals.
CLI command integration
src/vouch/cli.py
Adds contradict-scan with threshold, entity, dry-run, and limit options, then prints candidate results.
Pipeline validation and changelog
tests/test_contradictions.py, CHANGELOG.md
Tests discovery, filtering, proposal behavior, idempotency, limits, and scan-to-approve flow; documents the new command.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as contradict_scan
  participant Scanner as contradictions.scan
  participant Finder as find_candidates
  participant Store as KBStore
  participant Proposals as propose_relation
  User->>CLI: Run contradict-scan with options
  CLI->>Scanner: Call scan
  Scanner->>Finder: Find candidate pairs
  Finder->>Store: Read claims and existing contradiction records
  Finder-->>Scanner: Return scored candidates
  Scanner->>Proposals: Create pending proposal when writes are enabled
  Proposals-->>Scanner: Return proposal id or error
  Scanner-->>CLI: Return result rows
  CLI-->>User: Print candidates and proposal ids
Loading

Suggested reviewers: plind-junior

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: an advisory contradiction scanner for conflicting approved claims.
Linked Issues check ✅ Passed The PR matches #314 by adding a read-only scanner, CLI options, proposal creation on non-dry runs, and tests for duplicate prevention and approval flow.
Out of Scope Changes check ✅ Passed The changes stay scoped to the scanner, CLI, changelog, and tests, with no unrelated features or storage-layer business logic added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 16: Capitalize the word after the period in the changelog sentence so it
reads as a new sentence; update the text containing `--entity`, `--limit`, and
the following `scoring` phrase to start with an uppercase letter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 66e8ef38-6b6c-401c-87d9-3049d7e67505

📥 Commits

Reviewing files that changed from the base of the PR and between 36f035e and 1364e17.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/vouch/cli.py
  • src/vouch/contradictions.py
  • tests/test_contradictions.py

Comment thread CHANGELOG.md
@jsdevninja

Copy link
Copy Markdown
Contributor Author

@plind-junior Would you review please?

@plind-junior
plind-junior changed the base branch from main to test July 13, 2026 05:42
@github-actions github-actions Bot added ci github actions and automation mcp mcp, jsonl, and http surfaces storage kb storage, migrations, schemas, and proposals retrieval context, search, synthesis, and evaluation embeddings embedding-backed retrieval size: XL 1000 or more changed non-doc lines and removed size: M 200-499 changed non-doc lines labels Jul 13, 2026
@jsdevninja
jsdevninja force-pushed the feat/contradiction-scan branch 2 times, most recently from a673128 to c6eeb5a Compare July 13, 2026 15:49
@github-actions github-actions Bot added size: M 200-499 changed non-doc lines and removed ci github actions and automation mcp mcp, jsonl, and http surfaces storage kb storage, migrations, schemas, and proposals retrieval context, search, synthesis, and evaluation embeddings embedding-backed retrieval size: XL 1000 or more changed non-doc lines labels Jul 13, 2026
@jsdevninja

Copy link
Copy Markdown
Contributor Author

@plind-junior pleaes review

@jsdevninja
jsdevninja force-pushed the feat/contradiction-scan branch from c6eeb5a to c9d6c9c Compare July 13, 2026 16:27
walk approved claims grouped by shared entity, flag same-topic pairs
that disagree in polarity, and file a pending `contradicts` relation
proposal per surviving pair via proposals.propose_relation. read-only
and advisory: the scanner never sets ClaimStatus.CONTESTED, never
writes a Relation, and never touches Claim.contradicts — those only
land once a human runs `vouch approve` (or the pre-existing manual
lifecycle.contradict). scoring/grouping lives in the new
src/vouch/contradictions.py, not storage.py.

`vouch contradict-scan` mirrors `vouch dedup`'s cli-only shape:
--threshold, --entity, --limit, and --dry-run/--no-dry-run (default
dry-run). a pair already cross-linked via Claim.contradicts, joined
by an approved contradicts edge, or with a pending contradicts
proposal is skipped so repeat scans don't duplicate proposals.

closes vouchdev#314
@jsdevninja
jsdevninja force-pushed the feat/contradiction-scan branch from c9d6c9c to b85ab9b Compare July 15, 2026 11:41
@jsdevninja

Copy link
Copy Markdown
Contributor Author

@plind-junior Please review

@jsdevninja
jsdevninja requested a review from plind-junior as a code owner July 16, 2026 04:02
@plind-junior
plind-junior enabled auto-merge July 17, 2026 02:08
@plind-junior plind-junior added auto-pr auto-pr orchestration auto-merge owner-authorized: arm native auto-merge for non-core PRs labels Jul 17, 2026
@plind-junior

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions github-actions Bot removed the auto-pr auto-pr orchestration label Jul 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 164-171: Move the vouch contradict-scan changelog entry from the
[1.3.0] section into [Unreleased], preserving its existing text and formatting.

In `@src/vouch/contradictions.py`:
- Line 25: Convert the prose in the comments at src/vouch/contradictions.py
lines 25-25, 128-130, and 183-184, and src/vouch/cli.py lines 243-244 and
262-262 to lowercase, preserving their wording and comment structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d01bc0ad-79ec-49f5-bd6b-8ee8375e9ced

📥 Commits

Reviewing files that changed from the base of the PR and between f0c7f3f and 561ac2f.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/vouch/cli.py
  • src/vouch/contradictions.py
  • tests/test_contradictions.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_contradictions.py

Comment thread CHANGELOG.md
Comment on lines +164 to +171
- `vouch contradict-scan` — an offline scanner that groups approved claims
by shared entity and heuristically flags same-topic pairs that disagree
in polarity. `--dry-run` (default) only prints candidates; without it,
each surviving pair files a pending `contradicts` relation proposal via
`proposals.propose_relation` for a human `vouch approve` — the scanner
itself never writes a `Relation` or a `CONTESTED` status. `--threshold`,
`--entity`, and `--limit` tune the scan. scoring lives in the new
`src/vouch/contradictions.py`. (#314)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move the changelog entry to the [Unreleased] section.

As per coding guidelines, when adding a user-visible feature, the changelog entry must be placed under [Unreleased]. Currently, it is placed under the [1.3.0] release section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 164 - 171, Move the vouch contradict-scan
changelog entry from the [1.3.0] section into [Unreleased], preserving its
existing text and formatting.

Source: Coding guidelines


DEFAULT_THRESHOLD = 0.3

# No-longer-live assertions aren't worth flagging against.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Convert comment prose to lowercase.

Several code comments begin with uppercase letters. As per path instructions, keep prose lowercase in code comments and review notes.

  • src/vouch/contradictions.py#L25-L25: downcase the comment text.
  • src/vouch/contradictions.py#L128-L130: downcase the comment text.
  • src/vouch/contradictions.py#L183-L184: downcase the comment text.
  • src/vouch/cli.py#L243-L244: downcase the comment text.
  • src/vouch/cli.py#L262-L262: downcase the comment text.
📍 Affects 2 files
  • src/vouch/contradictions.py#L25-L25 (this comment)
  • src/vouch/contradictions.py#L128-L130
  • src/vouch/contradictions.py#L183-L184
  • src/vouch/cli.py#L243-L244
  • src/vouch/cli.py#L262-L262
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/contradictions.py` at line 25, Convert the prose in the comments at
src/vouch/contradictions.py lines 25-25, 128-130, and 183-184, and
src/vouch/cli.py lines 243-244 and 262-262 to lowercase, preserving their
wording and comment structure.

Source: Path instructions

@github-actions
github-actions Bot disabled auto-merge July 17, 2026 04:41
@github-actions github-actions Bot removed the auto-merge owner-authorized: arm native auto-merge for non-core PRs label Jul 17, 2026
@github-actions

Copy link
Copy Markdown

new commits were pushed after authorization. auto-merge is disarmed and the label removed — re-add the auto-merge label (or comment /auto-merge) to re-arm on the new head.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface docs documentation, specs, examples, and repo guidance size: M 200-499 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: contradiction finder — scan the kb and propose contradicts links for conflicting claims

2 participants